package cn.eu.common.query;

import cn.eu.common.query.annotation.Join;
import cn.eu.common.query.annotation.Query;
import cn.eu.common.query.annotation.QueryJoin;
import cn.eu.common.query.model.*;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.github.yulichang.wrapper.JoinAbstractLambdaWrapper;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Field;
import java.util.*;

@Slf4j
public class MpJoinQueryHelper {

    private static final Map<Class<?>, QueryJoinModel> QUERY_JOIN_CACHE = new HashMap<>();

    private static final Map<Class<?>, List<QueryConditionModel>> QUERY_CACHE = new HashMap<>();

    public static <T> MPJLambdaWrapper<T> buildQueryWrapper(Object criteria) {
        QueryJoinModel queryJoinModel = analysis(criteria);
        if(queryJoinModel != null) {
            TableModel main = queryJoinModel.getMain();
            MPJLambdaWrapper<T> wrapper = (MPJLambdaWrapper<T>) new MPJLambdaWrapper<>(main.getTableClass(), main.getAlias());
            wrapper.disableLogicDel().disableSubLogicDel();
            fillLogicDel(wrapper, main);
            fillJoinWrapper(wrapper, queryJoinModel.getJoin());
            fillWhereWrapper(wrapper, criteria, analysisQueryCondition(criteria, queryJoinModel));
            return wrapper;
        }
        throw new RuntimeException("criteria is null");
    }

    private static <T> void fillJoinWrapper(JoinAbstractLambdaWrapper<T, ?> wrapper,
                                            List<JoinModel> joins) {
        if(joins != null && !joins.isEmpty()) {
            for (JoinModel join : joins) {
                Join.Mode mode = join.getMode();
                TableModel right = join.getRight();
                wrapper.join(mode.getKeyWord(), right.getTableClass(), right.getAlias(), on -> {
                    fillConditionWrapper(on, join.getOn());
                    fillLogicDel(on, right);
                    return on;
                });
            }
        }
    }

    private static <T> void fillConditionWrapper(JoinAbstractLambdaWrapper<T, ?> wrapper,
                                                 List<ConditionModel> conditions) {
        if(wrapper != null && conditions != null && !conditions.isEmpty()) {
            for (ConditionModel condition : conditions) {
                Query.Type type = condition.getType();
                String leftColumn = StrUtil.format("{}.{}", condition.getLeftAlias(), condition.getLeftField());
                Object value = condition.getValue();
                if(value == null) {
                    value = StrUtil.format("{}.{}", condition.getRightAlias(), condition.getRightField());
                }
                fillWrapper(wrapper, type, leftColumn, value);
            }
        }
    }

    private static <T> void fillWhereWrapper(JoinAbstractLambdaWrapper<T, ?> wrapper,
                                             Object criteria,
                                             List<QueryConditionModel> queryConditionModels) {
        for (QueryConditionModel queryConditionModel : queryConditionModels) {
            Object value = BeanUtil.getFieldValue(criteria, queryConditionModel.getFieldName());
            List<ConditionModel> conditions = queryConditionModel.getConditions();
            if(conditions.size() > 1) {
                wrapper.and(w -> {
                    for (ConditionModel condition : conditions) {
                        Query.Type type = condition.getType();
                        fillWrapper(w.or(), type, condition.getLeftColumn(), value);
                    }
                });
            } else if(conditions.size() == 1) {
                ConditionModel conditionModel = conditions.get(0);
                fillWrapper(wrapper, conditionModel.getType(), conditionModel.getLeftColumn(), value);
            }
        }
    }

    private static <T> void fillWrapper(JoinAbstractLambdaWrapper<T, ?> wrapper,
                                            Query.Type type,
                                            String leftColumn,
                                            Object value) {
        type.setCondition(wrapper, leftColumn, value);
    }

    private static <T> void fillLogicDel(JoinAbstractLambdaWrapper<T, ?> wrapper, TableModel tableModel) {
        String logicDeleteSql = tableModel.getLogicDeleteSql();
        if(StrUtil.isNotBlank(logicDeleteSql)) {
            wrapper.apply(logicDeleteSql);
        }
    }

    private static QueryJoinModel analysis(Object criteria) {
        if(criteria != null) {
            Class<?> criteriaClass = criteria.getClass();
            if(!QUERY_JOIN_CACHE.containsKey(criteriaClass)) {
                QueryJoin queryJoin = criteriaClass.getAnnotation(QueryJoin.class);
                if(queryJoin != null) {
                    QUERY_JOIN_CACHE.put(criteriaClass, new QueryJoinModel(queryJoin));
                }
            }
            return QUERY_JOIN_CACHE.get(criteriaClass);
        }
        return null;
    }

    private static List<QueryConditionModel> analysisQueryCondition(Object criteria, QueryJoinModel queryJoinModel) {
        if(criteria != null) {
            Class<?> criteriaClass = criteria.getClass();
            if(!QUERY_CACHE.containsKey(criteriaClass)) {
                Map<String, TableModel> tableModelMap = queryJoinModel.getTableModelMap();
                String mainTableAlias = queryJoinModel.getMain().getAlias();
                List<QueryConditionModel> queryConditionModels = new ArrayList<>();
                for (Field field : ReflectUtil.getFields(criteriaClass)) {
                    Query query = field.getAnnotation(Query.class);
                    if(query != null) {
                        String fieldName = field.getName();
                        queryConditionModels.add(new QueryConditionModel(tableModelMap, mainTableAlias, fieldName, query));
                    }
                }
                QUERY_CACHE.put(criteriaClass, queryConditionModels);
            }
            return QUERY_CACHE.get(criteriaClass);
        }
        return null;
    }
}
